;//////////////////////////////////////////////////////////////////
; 主引导程序
;
; 程序功能：
; # 建立GDT
; # 创建保护模式下的描述符
; # 开启处理器保护机制
; # 加载内核
; # 创建内核段描述符
;//////////////////////////////////////////////////////////////////

    LBA EQU 1               ; 内核所在扇区号
    ImageBase EQU 0x40000   ; 内核加载的默认起始内存地址

section code vstart=0
    mov eax, cs:[GdtBase+0x7C00]
    xor edx, edx
    mov ebx, 0x10
    div ebx
    mov ds, eax     ; ds = GDT的起始地址
    mov ebx, edx    ; ebx = GDT中的相对地址

    ; 创建空描述符
    mov dword [ebx], 0x0
    mov dword [ebx+0x4], 0x0
    ; 创建4G数据段描述符(0~ffffffff)
    mov dword [ebx+0x8], 0x0000FFFF
    mov dword [ebx+0xC], 0x00CF9200
    ; 创建保护模式下MBR的代码段描述符(7c00~7dff)
    mov dword [ebx+0x10], 0x7C0001FF
    mov dword [ebx+0x14], 0x00409800
    ; 创建保护模式下栈段描述符（向下扩展）(7bff~6c00)
    mov dword [ebx+0x18], 0x7C00FFFE
    mov dword [ebx+0x1C], 0x00CF9600
    ; 创建保护模式下显示文本段描述符(b8000~bffff)
    mov dword [ebx+0x20], 0x80007FFF
    mov dword [ebx+0x24], 0x0040920B

    ; 加载到GDTR
    mov word cs:[GdtSize+0x7C00], 39    ; limit: 5 * 8 - 1
    lgdt cs:[GdtSize+0x7C00]

    ; 开启A20地址线
    in al, 0x92
    or al, 00000010B
    out 0x92, al

    ; 关闭中断机制
    cli
    
    ; 设置CR0.PE
    mov eax, CR0
    or  eax, 0x1
    mov CR0, eax

    jmp 0x10:@32    ; cs = 7c00~7dff
    bits 32
@32:
    mov eax, 0x8
    mov ds,  eax    ; ds = 0~FFFFFFFF
    mov eax, 0x18
    mov ss,  eax    ; ss = 7c00~6c00
    mov esp, 0

    ; 将内核加载到内存
    mov eax, LBA    ; eax = 内核所在LBA
    mov ebx, ImageBase  ; ebx = 内核加载的内存起始地址
    call ReadDisk

    ; 读取剩余扇区
    mov edi, ImageBase  ; edi = 内核加载的内存起始地址
    mov eax, [edi]      ; eax = 内核程序的总大小
    xor edx, edx
    mov ecx, 512
    div ecx

    cmp edx, 0
    jnz @1
    dec eax
@1:
    cmp eax, 0
    jz @Setup

    mov ecx, eax        ; ecx = 剩余扇区数
    mov eax, LBA
    inc eax             ; eax = 第二个扇区
@2:
    call ReadDisk
    inc eax             ; eax = 下一个扇区
    loop @2

    ; 为内核创建描述符
@Setup:
    mov esi, [GdtBase+0x7C00]

    ; 创建内核例程段描述符
    mov eax, [edi+0x4]
    mov ebx, [edi+0x8]
    sub ebx, eax        
    dec ebx             ; ebx = 段界限
    add eax, ImageBase  ; eax = 内存中的例程段基址
    mov ecx, 0x00409800
    call MakeDescriptor
    mov [esi+0x28], eax
    mov [esi+0x2C], edx

    ; 创建内核数据段描述符
    mov eax, [edi+0x8]  
    mov ebx, [edi+0xC]  
    sub ebx, eax        
    dec ebx             ; ebx = 段界限
    add eax, ImageBase  ; eax = 内存中的数据段基址
    mov ecx, 0x00409200
    call MakeDescriptor
    mov [esi+0x30], eax
    mov [esi+0x34], edx

    ; 创建内核代码段描述符
    mov eax, [edi+0xC]  
    mov ebx, [edi+0x0]  
    sub ebx, eax        
    dec ebx             ; ebx = 段界限
    add eax, ImageBase  ; eax = 内存中的代码段基址
    mov ecx, 0x00409800
    call MakeDescriptor
    mov [esi+0x38], eax
    mov [esi+0x3C], edx

    mov word [GdtSize+0x7C00], 63   ; limit: 8 * 8 - 1
    lgdt [GdtSize+0x7C00]

    jmp far [edi+0x10]

;//////////////////////////////////////////////////////////////////
; 读取扇区函数
; 输入：ebx-读取数据存放的起始地址；eax-LBA
; 返回：ebx-读取数据存放的起始地址
ReadDisk:
    push eax
    push ecx
    push edx
    
    ; 1F2
    push eax
    mov dx, 0x1F2  
    mov al, 1
    out dx, al
    pop eax
    ; 1F3
    mov dx, 0x1F3
    out dx, al
    ; 1F4
    mov dx, 0x1F4
    shr eax, 8
    out dx, al
    ; 1F5
    mov dx, 0x1F5
    shr eax, 8
    out dx, al
    ; 1F6
    mov dx, 0x1F6
    shr eax, 8
    or  al,  0xE0
    out dx, al
    ; 1F7
    mov dx, 0x1F7
    mov al, 0x20
    out dx, al

    ; 判断硬盘是否已准备好交换数据
@wait:
    in al, dx
    and al, 0x88
    cmp al, 0x8
    jnz @wait

    ; 将数据读取到指定地址
    mov ecx, 256
    mov dx,  0x1F0
@read:
    in  ax, dx
    mov [ebx], ax
    add ebx, 2
    loop @read

    pop edx
    pop ecx
    pop eax
    ret

;//////////////////////////////////////////////////////////////////
; 创建描述符
; 输入：eax-段基址；ebx-段界限；ecx-段属性
; 输出：edx:eax-段描述符
MakeDescriptor:
    push ebx
    push ecx

    mov edx, eax    ; edx = 段基址
    shl eax, 16
    or  ax,  bx     ; eax = 描述符低32位

    xor dx, dx
    rol edx, 8
    bswap edx
    xor bx, bx
    or  edx, ebx
    or  edx, ecx    ; edx = 描述符高32位

    pop  ecx
    pop  ebx
    ret

;//////////////////////////////////////////////////////////////////
; GDT界限
GdtSize dw 0
; GDT基址
GdtBase dd 0x00007E00

times 510-($-$$) db 0x0
dw 0xAA55
